﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using iStudio.Engine.Utils;

namespace iStudio.Engine
{
	public class BlockFile : IDisposable
	{
		object _lockObj;
		Stream _stream;
		BlockFileHeader _header;
		byte[] _byteStorage;
		float[] _floatStorage;
		int sampleSize = 4; //ieeefloat32 -> 4 bytes per sample

		public BlockFileHeader Header
		{
			get { return _header; }
		}

		public int Channels
		{
			get { return Header.Channels; }
		}

		public int SampleRate
		{
			get { return Header.SampleRate; }
		}

		public Stream Stream
		{
			get { return _stream; }
		}

		public static BlockFile CreateNew(string filename, BlockFileHeader header)
		{
			Stream stream = File.Open(filename, FileMode.CreateNew, FileAccess.ReadWrite);
			header.WriteToStream(stream);
			return new BlockFile(stream, header);
		}

		public static BlockFile Open(string filename)
		{
			Stream stream = File.Open(filename, FileMode.Open, FileAccess.ReadWrite);
			BlockFileHeader header = new BlockFileHeader();
			header.ReadFromStream(stream);
			return new BlockFile(stream, header);
		}

		protected BlockFile(Stream stream, BlockFileHeader header)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");
			if (!stream.CanRead)
				throw new ArgumentException("stream not readable");
			if (!stream.CanWrite)
				throw new ArgumentException("stream not writeable");
			if (header == null)
				throw new ArgumentNullException("header");

			//create a thread-safe stream
			stream = Stream.Synchronized(stream);

			_stream = stream;
			_header = header;

			Position = 0;
			_lockObj = new object();
		}

		public long Position
		{
			get
			{
				return (Stream.Position - (long)Header.DataOffset) / Header.BlockAlign;
			}
			set
			{
				if(value < 0)
					throw new ArgumentOutOfRangeException("value");
				Stream.Position = (long)Header.DataOffset + (value * Header.BlockAlign);
			}
		}

		public long Length
		{
			get
			{
				return (Stream.Length - (long)Header.DataOffset) / Header.BlockAlign;
			}
		}

		public void Write(float[] buffer, int offset, int count)
		{
			if (Header.Encoding != BlockFileEncoding.IeeeFloat32)
				throw new InvalidOperationException("Encoding has to be IeeeFloat32");

			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (buffer.Length < count)
				throw new ArgumentException("length", "buffer");
			if (count % Header.Channels != 0)
				throw new ArgumentOutOfRangeException("count");
			if (offset % Header.Channels != 0)
				throw new ArgumentOutOfRangeException("offset");

			_stream.Write(Extensions.ToByteArray(buffer), offset * 4, count * 4);
		}

		public void Write(byte[] buffer, int offset, int count)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (buffer.Length < count)
				throw new ArgumentException("length", "buffer");
			if (count % Header.BlockAlign != 0)
				throw new ArgumentOutOfRangeException("count");
			if (offset % Header.BlockAlign != 0)
				throw new ArgumentOutOfRangeException("offset");

			_stream.Write(buffer, offset, count);
		}

		public unsafe int Read(float[] buffer, int offset, int count)
		{
			if (Header.Encoding != BlockFileEncoding.IeeeFloat32)
				throw new InvalidOperationException("Encoding has to be IeeeFloat32");

			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (buffer.Length < count)
				throw new ArgumentException("length", "buffer");
			if (count % Header.Channels != 0)
				throw new ArgumentOutOfRangeException("count");
			if (offset % Header.Channels != 0)
				throw new ArgumentOutOfRangeException("offset");

			_byteStorage = _byteStorage.CheckForSize(count * 4);
			int read = _stream.Read(_byteStorage, offset, count * 4);
			if (read > 4)
				_byteStorage.CopyToFloatArray(buffer, offset, read / 4);

			return read / 4;
		}

		public int Read(byte[] buffer, int offset, int count)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (buffer.Length < count)
				throw new ArgumentException("length", "buffer");
			if (count % Header.BlockAlign != 0)
				throw new ArgumentOutOfRangeException("count");
			if (offset % Header.BlockAlign != 0)
				throw new ArgumentOutOfRangeException("offset");

			return _stream.Read(buffer, offset, count);
		}

		private bool _disposed;
		public void Dispose()
		{
			if(!_disposed)
			{
				_disposed = true;
				
				Dispose(true);
				GC.SuppressFinalize(this);
			}
		}

		protected virtual void Dispose(bool disposing)
		{
			if (_stream != null)
			{
				_stream.Dispose();
				_stream = null;
			}
		}

		~BlockFile()
		{
			Dispose(false);
		}
	}
}
